<h3>Chapter 3: Complex Data Types</h3>
<p><strong>3.1 Simple Data Types</strong></p>
<p>In the textbook LPC Basics, you learned
about the common, basic LPC data types: int, string, object, void. Most
important you learned that many operations and functions behave differently
based on the data type of the variables upon which they are operating. Some
operators and functions will even give errors if you use them with the wrong
data types. For example, "a"+"b" is handled much differently than 1+1. When
you ass "a"+"b", you are adding "b" onto the end of "a" to get "ab". On
the other hand, when you add 1+1, you do not get 11, you get 2 as you would
expect.
</p>
<p>
I refer to these data types as simple data types, because they
atomic in that they cannot be broken down into smaller component data types.
The object data type is a sort of exception, but you really cannot refer
individually to the components which make it up, so I refer to it as a simple
data type.
</p>
<p>
This chapter introduces the concept of the complex data type,
a data type which is made up of units of simple data types. LPC has two
common complex data types, both kinds of arrays. First, there is the traditional
array which stores values in consecutive elements accessed by a number representing
which element they are stored in. Second is an associative array called
a mapping. A mapping associates to values together to allow a more natural
access to data.
</p>
<hr size="1">
<p><strong>3.2 The Values NULL and 0</strong></p>
<p>
Before getting fully into arrays, there first should be a full understanding
of the concept of NULL versus the concept of 0. In LPC, a null value is
represented by the integer 0. Although the integer 0 and NULL are often freely
interchangeable, this interchangeability often leads to some great confusion
when you get into the realm of complex data types. You may have even
encountered such confusion while using strings.
</p>
<p>
0 represents a value which for integers means the value you add to another
value yet still retain the value added. This for any addition operation on
any data type, the ZERO value for that data type is the value that you can
add to any other value and get the original value. Thus: A plus ZERO equals
A where A is some value of a given data type and ZERO is the ZERO value for
that data type. This is not any sort of official mathematical definition.
There exists one, but I am not a mathematician, so I have no idea what the
term is. Thus for integers, 0 is the ZERO value since 1 + 0 equals 1.
</p>
<p>
NULL, on the other hand, is the absence of any value or meaning. The LPC
driver will interpret NULL as an integer 0 if it can make sense of it in
that context. In any context besides integer addition, A plus NULL causes
an error. NULL causes an error because adding valueless fields in other
data types to those data types makes no sense.
</p>
<p>Looking at this from another point of view, we can get the
ZERO value for strings by knowing what added to "a" will give us "a" as
a result. The answer is not 0, but instead "". With integers, interchanging
NULL and 0 was acceptable since 0 represents no value with respect to the
integer data type. This interchangeability is not true for other data types,
since their ZERO values do not represent no value. Namely, "" represents
a string of no length and is very different from 0.
</p>
<p>
When you first declare any variable of any type, it has no
value. Any data type except integers therefore must be initialized somehow
before you perform any operation on it. Generally, initialization is done
in the create() function for global variables, or at the top of the local
function for local variables by assigning them some value, often the ZERO
value for that data type. For example, in the following code I want to build
a string with random words:</p>
<pre>
string build_nonsense() {
    string str;
    int i;

    str = ""; /* Here str is initialized to the string ZERO value */

    for( i = 0; i < 6; i++ ) {
        switch( random( 3 ) + 1 ) {
            case 1: str += "bing"; break;
            case 2: str += "borg"; break;
            case 3: str += "foo"; break;
        }

        if( i == 5 )
            str += ".\n";
        else
            str += " ";
    }

    return capitalize(str);

}
</pre>
<p>
If we had not initialized the variable str, an error would
have resulted from trying to add a string to a NULL value. Instead, this
code first initializes str to the ZERO value for strings, "". After that,
it enters a loop which makes 6 cycles, each time randomly adding one of
three possible words to the string. For all words except the last, an additional
blank character is added. For the last word, a period and a return character
are added. The function then exits the loop, capitalizes the nonsense string,
then exits.
</p>
<hr size="1">
<p><strong>3.3 Arrays in LPC</strong></p>
<p>An array is a powerful complex data type
of LPC which allows you to access multiple values through a single variable.
For instance, Nightmare has an indefinite number of currencies in which
players may do business. Only five of those currencies, however, can be
considered hard currencies. A hard currency for the sake of this example
is a currency which is readily exchangeable for any other hard currency,
whereas a soft currency may only be bought, but not sold. In the bank, there
is a list of hard currencies to allow bank keepers to know which currencies
are in fact hard currencies. With simple data types, we would have to perform
the following nasty operation for every exchange transaction:
</p>
<pre>
int exchange( string str ) {
    string from, to;
    int amt;

    if( !str )
        return 0;

    if( sscanf( str, "%d %s for %s", amt, from, to ) != 3 )
        return 0;

    if( from != "platinum" && from != "gold" && from != "silver" &&
        from != "electrum" && from != "copper") {
        notify_fail("We do not buy soft currencies!\n");
        return 0;
    }

    .....

}
</pre>
<p>
With five hard currencies, we have a rather simple example. After all
it took only two lines of code to represent the if statement which filtered
out bad currencies. But what if you had to check against all the names which
cannot be used to make characters in the game? There might be 100 of those;
would you want to write a 100 part if statement? What if you wanted to add
a currency to the list of hard currencies? That means you would have to
change every check in the game for hard currencies to add one more part
to the if clauses. Arrays allow you simple access to groups of related data
so that you do not have to deal with each individual value every time you
want to perform a group operation.
</p>
<p>
As a constant, an array might look like this:
<blockquote>
({ "platinum", "gold", "silver", "electrum", "copper" })
</blockquote>
which is an array of type string. Individual data values in arrays are
called elements, or sometimes members. In code, just as constant strings
are represented by surrounding them with "", constant arrays are represented
by being surrounded by ({ }), with individual elements of the array being
separated by a ,.
</p>
<p>
You may have arrays of any LPC data type, simple or complex. Arrays made
up of mixes of values are called arrays of mixed type. In most LPC drivers,
you declare an array using a throw-back to C language syntax for arrays.
This syntax is often confusing for LPC coders because the syntax has a meaning
in C that simply does not translate into LPC. Nevertheless, if we wanted
an array of type string, we would declare it in the following manner:
string *arr;</p>
<p>
In other words, the data type of the elements it will contain followed
by a space and an asterisk. Remember, however, that this newly declared
string array has a NULL value in it at the time of declaration.</p>
<hr size="1">
<p><strong>3.4 Using Arrays</strong></p>
You now should understand how to declare and recognize
an array in code. In order to understand how they work in code, let's review
the bank code, this time using arrays:
</p>
<pre>
int exchange( string str ) {
    string from, to;

    if( !str )
        return 0;

    if( sscanf( str, "%d %s for %s", amt, from, to ) != 3 )
        return 0;

    if( member_array( from, hard_currencies ) == -1 ) {
        notify_fail("We do not buy soft currencies!\n");
        return 0;
    }

    ....

}
</pre>
<p>
This code assumes hard_currencies is a global variable and is initialized
in create() as:
<blockquote>
hard_currencies = ({"platinum", "gold", "electrum", "silver", "copper"});
</blockquote>
Ideally, you would have hard currencies as a #define in a header file
for all objects to use, but #define is a topic for a later chapter.
</p>
<p>
Once you know what the member_array() efun does, this method certainly
is much easier to read as well as is much more efficient and easier to code.
In fact, you can probably guess what the member_array() efun does: It tells
you if a given value is a member of the array in question. Specifically
here, we want to know if the currency the player is trying to sell is an
element in the hard_curencies array. What might be confusing to you is,
not only does member_array() tell us if the value is an element in the array,
but it in fact tells us which element of the array the value is.
</p>
<p>
How does it tell you which element? It is easier to understand arrays
if you think of the array variable as holding a number. In the value above,
for the sake of argument, we will say that hard_currencies holds the value
179000. This value tells the driver where to look for the array hard_currencies
represents. Thus, hard_currencies points to a place where the array values
may be found. When someone is talking about the first element of the array,
they want the element located at 179000. When the object needs the value
of the second element of the array, it looks at 179000 + one value, then
179000 plus two values for the third, and so on. We can therefore access
individual elements of an array by their index, which is the number of values
beyond the starting point of the array we need to look to find the value.
For the array hard_currencies array:
<blockquote>
"platinum" has an index of 0.<br>
"gold" has an index of 1.<br>
"electrum" has an index of 2.<br>
"silver" has an index of 3.<br>
"copper" has an index of 4.
</blockquote>
</p>
<p>
The efun member_array() thus returns the index of the element being tested
if it is in the array, or -1 if it is not in the array. In order to reference
an individual element in an array, you use its index number in the following
manner: array_name[index_no]
<blockquote>
Example: hard_currencies[3], where hard_currencies[3] would refer to "silver".
</blockquote>
</p>
<p>
So, you now should now several ways in which arrays appear either as a
whole or as individual elements. As a whole, you refer to an array variable
by its name and an array constant by enclosing the array in ({ }) and separating
elements by ,. Individually, you refer to array variables by the array name
followed by the element's index number enclosed in [], and to array constants
in the same way you would refer to simple data types of the same type as
the constant. Examples:
<blockquote>
Whole arrays:
<blockquote>
variable: arr<br>
constant: ({"platinum", "gold", "electrum", "silver", "copper"})
</blockquote>
Individual members of arrays:
<blockquote>
variable: arr[2]<br>
constant: "electrum"
</blockquote>
</blockquote>
</p>
<p>
You can use these means of reference to do all the things you are used
to doing with other data types. You can assign values, use the values in
operations, pass the values as parameters to functions, and use the values
as return types. It is important to remember that when you are treating
an element alone as an individual, the individual element is not itself
an array (unless you are dealing with an array of arrays). In the example
above, the individual elements are strings. So that:
<blockquote>
str = arr[3] + " and " + arr[1];
</blockquote>
will create str to equal "silver and gold". Although this seems simple
enough, many people new to arrays start to run into trouble when trying
to add elements to an array. When you are treating an array as a whole and
you wish to add a new element to it, you must do it by adding another array.
</p>
<p>Note the following example:
<blockquote>
string str1, str2;<br>
string *arr;<br>
<br>
str1 = "hi";<br>
str2 = "bye";<br>
/* str1 + str2 equals "hibye" */<br>
<br>
arr = ({ str1 }) + ({ str2 });<br>
/* arr is equal to ({ str1, str2 }) */
</blockquote>
</p>
<p>
Before going any further, I have to note that this example gives an extremely
horrible way of building an array. You should set it: arr = ({ str1, str2 }).
The point of the example, however, is that you must add like types together.
If you try adding an element to an array as the data type it is, you will
get an error. Instead you have to treat it as an array of a single element.
</p>
<hr size="1">
<p><strong>3.5 Mappings</strong></p>
<p>
One of the major advances made in LPMuds since they were
created is the mapping data type. People alternately refer to them as associative
arrays. Practically speaking, a mapping allows you freedom from the association
of a numerical index to a value which arrays require. Instead, mappings
allow you to associate values with indices which actually have meaning to
you, much like a relational database.
</p>
<p>
In an array of 5 elements, you access those values solely by their integer
indices which cover the range 0 to 4. Imagine going back to the example
of money again. Players have money of different amounts and different types.
In the player object, you need a way to store the types of money that exist
as well as relate them to the amount of that currency type the player has.
The best way to do this with arrays would have been to store an array of
strings representing money types and an array of integers representing values
in the player object. This would result in CPU-eating ugly code like this:
</p>
<pre>
int query_money( string type ) {
    int i;

    i = member_array( type, currencies );

    if( i >- 1 && i < sizeof(amounts) ) /* sizeof efun returns # of elements */
        return amounts[i];
    else
        return 0;

}
</pre>
<p>
And that is a simple query function. Look at an add function:
</p>
<pre>
void add_money( string type, int amt ) {
    string *tmp1;
    int *tmp2;
    int i, x, j, maxj;

    i = member_array( type, currencies );

    if( i >= sizeof(amounts) ) /* corrupt data, we are in a bad way */
        return;
    else if( i == -1 ) {
        currencies += ({ type });
        amounts += ({ amt });
        return;
    } else {
        amounts[i] += amt;
        if( amounts[i] < 1 ) {
            tmp1 = allocate( sizeof(currencies) - 1 );
            tmp2 = allocate( sizeof(amounts) - 1 );
            for( j = 0, x = 0, maxj = sizeof(tmp1); j < maxj; j++ ) {
                if( j == i )
                    x = 1;
                tmp1[j] = currencies[j+x];
                tmp2[j] = amounts[j+x];
            }
            currencies = tmp1;
            amounts = tmp2;
        }
    }
}
</pre>
<p>
That is really some nasty code to perform the rather simple concept of
adding some money. First, we figure out if the player has any of that kind
of money, and if so, which element of the currencies array it is. After
that, we have to check to see that the integrity of the currency data has
been maintained. If the index of the type in the currencies array is greater
than the highest index of the amounts array, then we have a problem since
the indices are our only way of relating the two arrays. Once we know our
data is in tact, if the currency type is not currently held by the player,
we simply tack on the type as a new element to the currencies array and
the amount as a new element to the amounts array. Finally, if it is a currency
the player currently has, we just add the amount to the corresponding index
in the amounts array. If the money gets below 1, meaning having no money
of that type, we want to clear the currency out of memory.
</p>
<p>
Subtracting an element from an array is no simple matter. Take, for example,
the result of the following:
<blockquote>
string *arr;<br>
<br>
arr = ({"a", "b", "a"});<br>
arr -= ({ arr[2] });
</blockquote>
What do you think the final value of arr is? Well, it is:
<blockquote>
({"b", "a"})
</blockquote>
</p>
<p>
Subtracting arr[2] from the original array does not remove the third element
from the array. Instead, it subtracts the value of the third element of
the array from the array. And array subtraction removes the first instance
of the value from the array. Since we do not want to be forced on counting
on the elements of the array as being unique, we are forced to go through
some somersaults to remove the correct element from both arrays in order
to maintain the correspondence of the indices in the two arrays.
</p>
<p>
Mappings provide a better way. They allow you to directly associate the
money type with its value. Some people think of mappings as arrays where
you are not restricted to integers as indices. Truth is, mappings are an
entirely different concept in storing aggregate information. Arrays force
you to choose an index which is meaningful to the machine for locating the
appropriate data. The indices tell the machine how many elements beyond
the first value the value you desire can be found. With mappings, you choose
indices which are meaningful to you without worrying about how that machine
locates and stores it.
</p>
<p>
You may recognize mappings in the following forms:
<blockquote>
constant values:<br>
whole: ([ index : value, index : value ]) Ex: ([ "gold":10, "silver":20 ])<br>
element: 10
</blockquote>
<blockquote>
variable values:<br>
whole: map (where map is the name of a mapping variable)<br>
element: map["gold"]
</blockquote>
So now my monetary functions would look like:
<pre>
int query_money( string type ) { return money[type]; }

void add_money( string type, int amt ) {
    if( !money[type] )
        money[type] = amt;
    else
        money[type] += amt;

    if( money[type] < 1 )
        map_delete( money, type ); /* this is for MudOS */

}
</pre>
<p>
Please notice first that the efuns for clearing a mapping element from
the mapping vary from driver to driver. Check with your driver's documentation
for the exact name an syntax of the relevant efun.
</p>
<p>
As you can see immediately, you do not need to check the integrity of
your data since the values which interest you are inextricably bound to
one another in the mapping. Secondly, getting rid of useless values is a
simple efun call rather than a tricky, CPU-eating loop. Finally, the query
function is made up solely of a return instruction.
</p>
<p>
You must declare and initialize any mapping before using it.
</p>
<p>
Declarations look like:
<blockquote>
mapping map;
</blockquote>
Whereas common initializations look like:
<blockquote>
map = ([ ]);<br>
map = allocate_mapping(10) ...OR... map = m_allocate(10);<br>
map = ([ "gold" : 20, "silver" : 15 ]);
</blockquote>
As with other data types, there are rules defining how they work in common
operations like addition and subtraction:
<blockquote>
([ "gold" : 20, "silver" : 30 ]) + ([ "electrum" : 5 ])
</blockquote>
gives:
<blockquote>
(["gold" : 20, "silver" : 30, "electrum" : 5 ])
</blockquote>
</p>
<p>
Although my demonstration shows a continuity of order, there is in fact
no guarantee of the order in which elements of mappings will stored. Equivalence
tests among mappings are therefore not a good thing.
</p>
<hr size="1">
<p><strong>3.6 Summary</strong></p>
<p>
Mappings and arrays can be built as complex as you need them
to be. You can have an array of mappings of arrays. Such a thing would be
declared like this:
<blockquote>
mapping *map_of_arrs;
</blockquote>
which might look like:
<blockquote>
({ ([ ind1 : ({ valA1, valA2 }), ind2 : ({ valB1, valB2 }) ]), ([ indX : ({ valX1, valX2 }) ]) })
</blockquote>
</p>
<p>
Mappings may use any data type as an index, including objects. Mapping
indices are often referred to as keys as well, a term from databases. Always
keep in mind that with any non-integer data type, you must first initialize
a variable before making use of it in common operations such as addition
and subtraction. In spite of the ease and dynamics added to LPC coding by
mappings and arrays, errors caused by failing to initialize their values
can be the most maddening experience for people new to these data types.
I would venture that a very high percentage of all errors people experimenting
with mappings and arrays for the first time encounter are one of three error
messages:
<blockquote>
Indexing on illegal type.<br>
Illegal index.<br>
Bad argument 1 to (+ += - -=) /* insert your favourite operator */
</blockquote>
</p>
<p>
Error messages 1 and 3 are darn near almost always caused by a failure
to initialize the array or mapping in question. Error message 2 is caused
generally when you are trying to use an index in an initialized array which
does not exist. Also, for arrays, often people new to arrays will get error
message 3 because they try to add a single element to an array by adding
the initial array to the single element value instead of adding an array
of the single element to the initial array. Remember, add only arrays to
arrays.
</p>
<p>
At this point, you should feel comfortable enough with mappings and arrays
to play with them. Expect to encounter the above error messages a lot when
first playing with these. The key to success with mappings is in debugging
all of these errors and seeing exactly what causes wholes in your programming
which allow you to try to work with uninitialized mappings and arrays. Finally,
go back through the basic room code and look at things like the set_exits()
(or the equivalent on your mudlib) function. Chances are it makes use of
mappings. In some instances, it will use arrays as well for compatibility
with mudlib.n.
</p>
